# coding: utf-8
from typing import Union

from PySide6.QtCore import QUrl, Signal, QSize, Property, Qt, QRectF
from PySide6.QtGui import QPixmap, QColor, QImage, QImageReader, QMovie, QPainter, QPainterPath, QPaintEvent
from PySide6.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest
from PySide6.QtWidgets import QGraphicsDropShadowEffect, QAbstractButton, QWidget, QLabel, QGraphicsBlurEffect
from qfluentwidgets import isDarkTheme, setFont
from qfluentwidgets.common.overload import singledispatchmethod

from common import UserAgent, OthersIcon


class ImageLabelBase:
    """
    Image label base

    Constructors
    ------------
    * ImageLabel(`parent`: QWidget = None)
    * ImageLabel(`image`: str | QImage | QPixmap, `parent`: QWidget = None)
    """
    finished = Signal()

    @singledispatchmethod
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)
        self._movie = None
        self._shadowVisible = None
        self.shadowEffect = None
        self._loadingPixmap = None
        self._errorPixmap = None
        self._url = None
        self.image = QImage()
        self.setBorderRadius(0, 0, 0, 0)

        self.networkAccessManager = QNetworkAccessManager(self)
        self.networkAccessManager.finished.connect(self.__onNetworkAccessManagerFinishedConnect)

        self._postInit()

    @__init__.register
    def _(self, image: str, parent=None):
        self.__init__(parent)
        self.setImage(image)
        self._postInit()

    @__init__.register
    def _(self, image: QImage, parent=None):
        self.__init__(parent)
        self.setImage(image)
        self._postInit()

    @__init__.register
    def _(self, image: QPixmap, parent=None):
        self.__init__(parent)
        self.setImage(image)
        self._postInit()

    def _postInit(self):
        pass

    def movie(self):
        return self._movie

    def _onFrameChanged(self, index: int):
        self.image = self.movie().currentImage()
        self.update()

    def setBorderRadius(self, topLeft: int, topRight: int, bottomLeft: int, bottomRight: int):
        """ set the border radius of image """
        self._topLeftRadius = topLeft
        self._topRightRadius = topRight
        self._bottomLeftRadius = bottomLeft
        self._bottomRightRadius = bottomRight
        self.update()

    def setImage(self, image: Union[str, QPixmap, QImage] = None):
        """ set the image of label """
        self.image = image or QImage()

        if isinstance(image, str):
            reader = QImageReader(image)
            if reader.supportsAnimation():
                self.setMovie(QMovie(image))
            else:
                self.image = reader.read()
        elif isinstance(image, QPixmap):
            self.image = image.toImage()

        # self.setFixedSize(self.image.size())
        self.update()

    def scaledToWidth(self, width: int):
        if self.isNull():
            return

        h = int(width / self.image.width() * self.image.height())
        self.setFixedSize(width, h)

        if self.movie():
            self.movie().setScaledSize(QSize(width, h))

    def scaledToHeight(self, height: int):
        if self.isNull():
            return

        w = int(height / self.image.height() * self.image.width())
        self.setFixedSize(w, height)

        if self.movie():
            self.movie().setScaledSize(QSize(w, height))

    def isNull(self):
        return self.image.isNull()

    def setPixmap(self, pixmap: Union[QPixmap, str]):
        self.setImage(pixmap)

    def pixmap(self) -> QPixmap:
        return QPixmap.fromImage(self.image)

    def setMovie(self, movie: QMovie):
        self._movie = movie
        movie.start()
        self.image = movie.currentImage()
        self.movie().frameChanged.connect(self._onFrameChanged)

    def paintEvent(self, e):
        if self.isNull():
            return

        painter = QPainter(self)
        painter.setRenderHints(QPainter.Antialiasing)

        path = QPainterPath()
        w, h = self.width(), self.height()

        # top line
        path.moveTo(self.topLeftRadius, 0)
        path.lineTo(w - self.topRightRadius, 0)

        # top right arc
        d = self.topRightRadius * 2
        path.arcTo(w - d, 0, d, d, 90, -90)

        # right line
        path.lineTo(w, h - self.bottomRightRadius)

        # bottom right arc
        d = self.bottomRightRadius * 2
        path.arcTo(w - d, h - d, d, d, 0, -90)

        # bottom line
        path.lineTo(self.bottomLeftRadius, h)

        # bottom left arc
        d = self.bottomLeftRadius * 2
        path.arcTo(0, h - d, d, d, -90, -90)

        # left line
        path.lineTo(0, self.topLeftRadius)

        # top left arc
        d = self.topLeftRadius * 2
        path.arcTo(0, 0, d, d, -180, -90)

        # draw image
        image = self.image.scaled(
            self.size() * self.devicePixelRatioF(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)

        painter.setPen(Qt.NoPen)
        painter.setClipPath(path)
        painter.drawImage(self.rect(), image)

    @Property(int)
    def topLeftRadius(self):
        return self._topLeftRadius

    @topLeftRadius.setter
    def topLeftRadius(self, radius: int):
        self.setBorderRadius(radius, self.topRightRadius, self.bottomLeftRadius, self.bottomRightRadius)

    @Property(int)
    def topRightRadius(self):
        return self._topRightRadius

    @topRightRadius.setter
    def topRightRadius(self, radius: int):
        self.setBorderRadius(self.topLeftRadius, radius, self.bottomLeftRadius, self.bottomRightRadius)

    @Property(int)
    def bottomLeftRadius(self):
        return self._bottomLeftRadius

    @bottomLeftRadius.setter
    def bottomLeftRadius(self, radius: int):
        self.setBorderRadius(self.topLeftRadius, self.topRightRadius, radius, self.bottomRightRadius)

    @Property(int)
    def bottomRightRadius(self):
        return self._bottomRightRadius

    @bottomRightRadius.setter
    def bottomRightRadius(self, radius: int):
        self.setBorderRadius(
            self.topLeftRadius, self.topRightRadius, self.bottomLeftRadius, radius)

    def setShadowEffect(self, blurRadius=30, offset=(0, 0), color=QColor(0, 0, 0, 100)):
        """
        为对话框添加阴影
        """
        self.shadowEffect = QGraphicsDropShadowEffect(self)
        self.shadowEffect.setBlurRadius(blurRadius)
        self.shadowEffect.setOffset(*offset)
        self.shadowEffect.setColor(color)
        self.setGraphicsEffect(None)
        self.setGraphicsEffect(self.shadowEffect)

    def isShadowVisible(self):
        return self._shadowVisible

    def setShadowVisible(self, visible: bool):
        self._shadowVisible = visible
        if self._shadowVisible:
            self.setShadowEffect()
        else:
            self.setGraphicsEffect(None)
            del self.shadowEffect
            self.shadowEffect = None

    def __onNetworkAccessManagerFinishedConnect(self, reply: QNetworkReply):
        pixmap = QPixmap()
        if reply.error() == QNetworkReply.NoError:
            pixmap.loadFromData(reply.readAll())
        if pixmap.isNull():
            if self._errorPixmap:
                pixmap = self._errorPixmap
            else:
                pixmap.load(OthersIcon.ImageLose.path())
        self.setPixmap(pixmap)
        self.finished.emit()
        reply.deleteLater()

    def setLoadingPixmap(self, pixmap: Union[str, QPixmap]):
        self._loadingPixmap = pixmap
        self.setPixmap(pixmap)
        self.update()

    def setErrorPixmap(self, pixmap: Union[str, QPixmap]):
        if issubclass(pixmap, str):
            pixmap = QPixmap(pixmap)
        self._errorPixmap = pixmap
        self.update()

    def setUrl(self, url: Union[str, QUrl], headers: dict = None):
        self._url = url
        request = QNetworkRequest(QUrl(url))
        request.setAttribute(QNetworkRequest.Attribute.RedirectionTargetAttribute, True)
        request.setRawHeader(b'user-agent', UserAgent.encode())
        if headers is not None:
            for key, value in headers.items():
                request.setRawHeader(key.encode(), value.encode())
        self.networkAccessManager.get(request)

    def url(self) -> QUrl:
        return self._url


class ImageLabel(ImageLabelBase, QLabel):
    clicked = Signal()

    def mouseReleaseEvent(self, e):
        super().mouseReleaseEvent(e)
        self.clicked.emit()


class ImageButton(ImageLabelBase, QAbstractButton):
    def leaveEvent(self, a0):
        self.setShadowVisible(False)

    def enterEvent(self, a0):
        self.setShadowVisible(True)


class WebImageButton(ImageButton):

    def _postInit(self):
        self.setLoadingPixmap(QPixmap(OthersIcon.Loading.path()))


class WebImageLabel(ImageLabel):

    def _postInit(self):
        self.setPixmap(QPixmap(OthersIcon.Loading.path()))


class BackgroundMaskLabel(ImageLabel):
    def _postInit(self):
        self._level = 160
        c = 0 if isDarkTheme() else 255
        self._maskColor = QColor(c, c, c, self._level)

        self.lower()

    def setColorOpacity(self, level: int):
        self._level = level
        self.update()

    def colorOpacity(self) -> int:
        return self._level

    def setBackgroundColor(self, color: QColor):
        self._maskColor = color
        self.update()

    def backgroundColor(self):
        return self._maskColor

    def setPixmap(self, pixmap: Union[str, QPixmap], size: QSize = None):
        if not pixmap:
            return
        if isinstance(pixmap, str):
            pixmap = QPixmap(pixmap)

        if pixmap.isNull():
            self.setVisible(False)
        else:
            self.setVisible(True)
        self.setImage(pixmap)
        if size:
            self.updateBackgroundImage(size)

    def paintEvent(self, event: QPaintEvent):
        super().paintEvent(event)
        painter = QPainter(self)
        painter.setRenderHints(QPainter.Antialiasing)
        self._maskColor.setAlpha(self._level)
        painter.setBrush(self._maskColor)
        xRadius = max(self.topLeftRadius, self.topRightRadius)
        yRadius = max(self.bottomLeftRadius, self.bottomRightRadius)
        painter.drawRoundedRect(QRectF(self.rect()), xRadius, yRadius)
        painter.end()

    def updateBackgroundImage(self, size: QSize):
        """
        更新背景图片
        :param size: 主窗口大小
        :return:
        """
        width, height = size.width(), size.height()
        self.scaledToWidth(width)
        if self.height() < height:
            self.scaledToHeight(height)
            x = (self.height() - height) // 2
        else:
            x = (self.width() - width) // 2
        self.move(-x, 0)


class LatestImageLabel(WebImageLabel):
    """ 最新图像标签 """

    def _postInit(self):
        super()._postInit()
        self.label = QLabel(self)
        self.label.move(0, self.height() - 20)
        self.label.setAlignment(Qt.AlignCenter)
        self.label.setVisible(False)

        self.label.setStyleSheet("QLabel{background-color: rgba(0, 0, 0, 160); border-radius: 5px;color: #ffffff;}")
        setFont(self.label, 10)
        self.setFixedHeight(20)
        self.setBorderRadius(5, 5, 5, 5)

    def setUpdateText(self, text: str):
        if text:
            self.label.setText(text)
            self.label.setVisible(True)
        else:
            self.label.setVisible(False)

    def resizeEvent(self, a0):
        super().resizeEvent(a0)
        self.label.resize(a0.size().width(), 20)
        self.label.move(0, a0.size().height() - 20)


class AvatarWidget(ImageLabel):
    """ Avatar widget

    Constructors
    ------------
    * AvatarWidget(`parent`: QWidget = None)
    * AvatarWidget(`image`: str | QImage | QPixmap, `parent`: QWidget = None)
    """

    def _postInit(self):
        super()._postInit()
        self.setRadius(48)

    def getRadius(self):
        return self._radius

    def setRadius(self, radius: int):
        self._radius = radius
        self.setFixedSize(2 * radius, 2 * radius)
        self.update()

    def paintEvent(self, e):
        if self.isNull():
            return

        painter = QPainter(self)
        painter.setRenderHints(QPainter.RenderHint.Antialiasing)

        # center crop image
        image = self.image.scaled(
            self.size() * self.devicePixelRatioF(), Qt.AspectRatioMode.KeepAspectRatioByExpanding,
            Qt.TransformationMode.SmoothTransformation)  # type: QImage

        iw, ih = image.width(), image.height()
        d = self.getRadius() * 2 * self.devicePixelRatioF()
        x, y = (iw - d) / 2, (ih - d) / 2
        image = image.copy(int(x), int(y), int(d), int(d))

        # draw image
        path = QPainterPath()
        path.addEllipse(QRectF(self.rect()))

        painter.setPen(Qt.NoPen)
        painter.setClipPath(path)
        painter.drawImage(self.rect(), image)

    radius = Property(int, getRadius, setRadius)


class CentreImageLabel(ImageLabel):
    """ 居中图像标签 """

    def paintEvent(self, e):
        if self.isNull():
            return

        painter = QPainter(self)
        painter.setRenderHints(QPainter.RenderHint.Antialiasing)

        # center crop image
        image = self.image.scaled(
            self.size() * self.devicePixelRatioF(),
            Qt.AspectRatioMode.KeepAspectRatioByExpanding,
            Qt.TransformationMode.SmoothTransformation
        )  # type: QImage

        iw, ih = image.width(), image.height()
        w = self.width() * self.devicePixelRatioF()
        h = self.height() * self.devicePixelRatioF()
        x, y = (iw - w) / 2, (ih - h) / 2
        image = image.copy(int(x), int(y), int(w), int(h))

        # draw image
        path = QPainterPath()
        w, h = self.width(), self.height()

        # top line
        path.moveTo(self.topLeftRadius, 0)
        path.lineTo(w - self.topRightRadius, 0)

        # top right arc
        d = self.topRightRadius * 2
        path.arcTo(w - d, 0, d, d, 90, -90)

        # right line
        path.lineTo(w, h - self.bottomRightRadius)

        # bottom right arc
        d = self.bottomRightRadius * 2
        path.arcTo(w - d, h - d, d, d, 0, -90)

        # bottom line
        path.lineTo(self.bottomLeftRadius, h)

        # bottom left arc
        d = self.bottomLeftRadius * 2
        path.arcTo(0, h - d, d, d, -90, -90)

        # left line
        path.lineTo(0, self.topLeftRadius)

        # top left arc
        d = self.topLeftRadius * 2
        path.arcTo(0, 0, d, d, -180, -90)

        painter.setPen(Qt.PenStyle.NoPen)
        painter.setClipPath(path)
        painter.drawImage(self.rect(), image)
        painter.end()


class WebCentreImageLabel(CentreImageLabel):
    """ web 居中图像标签 """

    def _postInit(self):
        super()._postInit()
        self.setLoadingPixmap(OthersIcon.Loading.path())


class WebAvatarWidget(AvatarWidget):
    """ web 头像控件 """

    def _postInit(self):
        super()._postInit()
        self.setLoadingPixmap(OthersIcon.Loading.path())


class PixmapLabel(QLabel):
    """ Label for high dpi pixmap """

    def __init__(self, parent=None):
        super().__init__(parent)
        self.__pixmap = QPixmap()

    def setPixmap(self, pixmap: Union[str, QPixmap]):
        if isinstance(pixmap, str):
            pixmap = QPixmap(pixmap)
        self.__pixmap = pixmap
        self.setFixedSize(pixmap.size())
        self.update()

    def pixmap(self):
        return self.__pixmap

    def paintEvent(self, e):
        if self.__pixmap.isNull():
            return super().paintEvent(e)

        painter = QPainter(self)
        painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
        painter.setPen(Qt.NoPen)
        painter.drawPixmap(self.rect(), self.__pixmap)


class BlurImageLabel(ImageLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._maskColor = QColor(0, 0, 0, 100)
        self.blurEffect = QGraphicsBlurEffect(self)
        self.setGraphicsEffect(None)
        self.setGraphicsEffect(self.blurEffect)

    def setBlurRadius(self, blurEffect: int):
        self.blurEffect.setBlurRadius(blurEffect)

    def setMaskColor(self, color: QColor):
        self._maskColor = color
        self.update()

    def maskColor(self) -> QColor:
        return self._maskColor

    def paintEvent(self, e):
        super().paintEvent(e)
        painter = QPainter(self)
        painter.setRenderHints(QPainter.Antialiasing)
        painter.setBrush(self._maskColor)
        xRadius = max(self.topLeftRadius, self.topRightRadius)
        yRadius = max(self.bottomLeftRadius, self.bottomRightRadius)
        painter.drawRoundedRect(QRectF(self.rect()), xRadius, yRadius)
        painter.end()


class AvatarBlurWidget(AvatarWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._maskColor = QColor(0, 0, 0, 100)
        self.blurEffect = QGraphicsBlurEffect(self)
        self.setGraphicsEffect(None)
        self.setGraphicsEffect(self.blurEffect)

    def setBlurRadius(self, blurEffect: int):
        self.blurEffect.setBlurRadius(blurEffect)

    def setMaskColor(self, color: QColor):
        self._maskColor = color
        self.update()

    def maskColor(self) -> QColor:
        return self._maskColor

    def paintEvent(self, e):
        super().paintEvent(e)
        painter = QPainter(self)
        painter.setRenderHints(QPainter.Antialiasing)
        painter.setBrush(self._maskColor)
        radius = self.getRadius()
        painter.drawRoundedRect(QRectF(self.rect()), radius, radius)
        painter.end()
